package main

import (
	"encoding/json"
	"log"
	"os"
	"path"
	"time"
	"github.com/boltdb/bolt"
)

type Store interface {
	Put(string, string, string) error
	GetTimes(string, string, string) ([]byte, error)
	GetStates(string, string) ([]byte, error)
	GetSources(string) ([]byte, error)
}

func NewStore(basepath, dbname string) (s Store, err error) {
	s, err = NewBoltStore(basepath, dbname)
	return
}

type BoltStore struct {
	fn string
	db *bolt.DB
}

func NewBoltStore(basepath, dbname string) (Store, error) {
	_, err := os.Stat(basepath)
	if os.IsNotExist(err) {
		err = os.MkdirAll(basepath, 0755)
	}
	if err != nil {
		return nil, err
	}
	fn := path.Join(basepath, dbname)
	db, err := bolt.Open(fn, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltStore{
		fn: fn, db: db,
	}, nil
}

func (b *BoltStore) Put(addr, source, state string) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		var bucket *bolt.Bucket
		var err error
		if bucket, err = tx.CreateBucketIfNotExists([]byte(addr)); err != nil {
			return err
		}
		if bucket, err = bucket.CreateBucketIfNotExists([]byte(source)); err != nil {
			return err
		}
		var val = time.Now().Format("2006-01-02 15:04:05")
		var v []byte
		if v = bucket.Get([]byte(state + ":init")); v == nil {
			if err = bucket.Put([]byte(state+":init"), []byte(val)); err != nil {
				return err
			}
			log.Println("New state.")
			log.Println(state + ":init -> " + val)
		}
		if err = bucket.Put([]byte(state+":last"), []byte(val)); err != nil {
			return err
		}
		log.Println(state + ":last -> " + val)
		/**********show**********/
		/*
		c := bucket.Cursor()
		for x, y := c.First(); x != nil; x, y = c.Next() {
			log.Printf("%s -> %s\n", x, y)
		}*/
		return nil
	})
}

func (b *BoltStore) GetTimes(addr string, source string, state string) ([]byte, error) {
	var bucket *bolt.Bucket
	ret := make(map[string]string)
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(addr)); bucket == nil {
			log.Println("Err: addr not found")
			return Status404NotFoundErr
		}
		if bucket = bucket.Bucket([]byte(source)); bucket == nil {
			log.Println("Err: source not found")
			return Status404NotFoundErr
		} else {
			timeInit := bucket.Get([]byte(state + ":init"))
			timeLast := bucket.Get([]byte(state + ":last"))
			if timeInit != nil && timeLast != nil {
				ret["init"] = string(timeInit)
				ret["last"] = string(timeLast)
			} else {
				log.Println("Err: state not found")
				return Status404NotFoundErr
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	show, _ := json.Marshal(&ret)
	log.Println(string(show))
	return json.Marshal(&ret)
}

func (b *BoltStore) GetStates(addr, source string) ([]byte, error) {
	var ret []string
	var bucket *bolt.Bucket
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(addr)); bucket == nil {
			log.Println("Err: addr not found")
			return Status404NotFoundErr
		}
		if bucket = bucket.Bucket([]byte(source)); bucket == nil {
			log.Println("Err: source not found")
			return Status404NotFoundErr
		}
		c := bucket.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			if string(k[len(k)-4:]) == "init" {
				state := k[:len(k)-5]
				ret = append(ret, string(state))
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	show, _ := json.Marshal(&ret)
	log.Println(string(show))
	return json.Marshal(&ret)
}

func (b *BoltStore) GetSources(addr string) ([]byte, error) {
	var ret []string
	var bucket *bolt.Bucket
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(addr)); bucket == nil {
			log.Println("Err: addr not found")
			return Status404NotFoundErr
		}
		c := bucket.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			ret = append(ret, string(k))
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	show, _ := json.Marshal(&ret)
	log.Println(string(show))
	return json.Marshal(&ret)
}

func (b *BoltStore) Close() error {
	return b.db.Close()
}
